package sketch.specs;

import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.TypeParameter;
import org.eclipse.jdt.internal.core.dom.NaiveASTFlattener;

import sketch.ast.ASTUtils;
import sketch.specs.annotation.AnnotationProcessor;
import sketch.specs.annotation.Repl;
import sketch.specs.annotation.Repls;
import sketch.util.Checker;
import sketch.util.Globals;

/**
 * Note it does not support qualified name right now.
 * */
public class MethodRepInstanitiations {
	
	public static String replace_name = "replace";
	
	public static boolean remove_repl = true;

	final MethodDeclaration method_declaration;
	
	final List<Map<String, String>> mapping_list;
	
	MethodRepInstanitiations(MethodDeclaration m, List<Map<String, String>> mapping_list) {
		Checker.checkNull(m, "The method could not be null.");
		Checker.checkNull(mapping_list, "The mapping_list could not be null.");
		this.method_declaration = m;
		this.mapping_list = mapping_list;
	}
	
	List<MethodDeclaration> getReplacedMethods() {
		List<MethodDeclaration> replicates = new LinkedList<MethodDeclaration>();
		int count = 0;
		for(Map<String, String> mappings : this.mapping_list) {
		    MethodDeclaration replicate = ASTUtils.deepClone(method_declaration);
		    replicate.modifiers().clear();
		    NameReplacementVisitor visitor = new NameReplacementVisitor(mappings);
		    replicate.accept(visitor);
		    //System.out.println("mappings: " + mappings + ", replace num: " + visitor.replacenum);
		    if(visitor.replacenum > 0) {
		      String methodName = replicate.getName().toString();
		      replicate.getName().setIdentifier(methodName + "_" + replace_name + "_" + (count++));
		      replicates.add(replicate);
		      if(remove_repl) {
		    	ASTUtils.removeMethodAnnotation(replicate, Repl.class);
		    	ASTUtils.removeMethodAnnotation(replicate, Repls.class);
		      }
		    }
		}
		return replicates;
	}
	
	//XXX limitation: have not supported netsted class yet
	// another limitation a compilation unit has only one class
	public static CompilationUnit getCompilationUnit(String className, String fileName) {
		StringBuilder sb = new StringBuilder();
		//seems the only way is to write to a temp file, and then read it back
		Class<?> clz = null;
		try {
			clz = Class.forName(className);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("Class : " + className + " could not be found!");
		}
		AnnotationProcessor processor = new AnnotationProcessor(clz);
		processor.process();
		
		CompilationUnit unit = ASTUtils.astRoot(fileName);
		List<AbstractTypeDeclaration> types = unit.types();
		Checker.checkTrue(types.size() == 1, "currently only support one class per compilation");
		List<MethodDeclaration> methods = ASTUtils.getAllMethods(unit);
		
		//keep a map between original method and its replaced one
		Map<MethodDeclaration, List<MethodDeclaration>> map = new LinkedHashMap<MethodDeclaration, List<MethodDeclaration>>();
		for(Method ms : clz.getDeclaredMethods()) {
			List<Map<String, String>> mapping_list = processor.getMappings(ms);
			MethodDeclaration m = getJUnitMethodDeclaration(ms, methods);
			if(mapping_list == null || m == null) {
				continue;
			}
			MethodRepInstanitiations instanitiation = new MethodRepInstanitiations(m, mapping_list);
			List<MethodDeclaration> method_declares = instanitiation.getReplacedMethods();
			map.put(m, method_declares);
		}
		
		AppendExpandedMethodVisitor visitor = new AppendExpandedMethodVisitor(unit, map);
		unit.accept(visitor);
		
		String str = visitor.getResult();
		//System.out.println(str);
		
		return ASTUtils.parseUnit(str);
	}
	
	public static String instantiate(String className, String filePath) {
		StringBuilder sb = new StringBuilder();
		
		Class<?> clz = null;
		try {
			clz = Class.forName(className);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("Class : " + className + " could not be found!");
		}
		AnnotationProcessor processor = new AnnotationProcessor(clz);
		processor.process();
		//a list of method
		List<MethodDeclaration> methods = ASTUtils.getAllMethods(ASTUtils.astRoot(filePath));
		
		for(Method ms : clz.getDeclaredMethods()) {
			List<Map<String, String>> mapping_list = processor.getMappings(ms);
			MethodDeclaration m = getJUnitMethodDeclaration(ms, methods);
			if( mapping_list == null) {
				continue;
			}
			if(m == null) {
				continue;
			}
			MethodRepInstanitiations instanitiation = new MethodRepInstanitiations(m, mapping_list);
			List<MethodDeclaration> method_declares = instanitiation.getReplacedMethods();
			int count = 0;
			for(MethodDeclaration method_declare : method_declares) {
			  method_declare.getName().setIdentifier(method_declare.getName().getIdentifier() + "_replaced_" + (count++));
			  sb.append(method_declare.toString());
			}
		}
		return sb.toString();
	}
	
	//only need to compare names, since JUNit method does not have parameters
	private static MethodDeclaration getJUnitMethodDeclaration(Method method, List<MethodDeclaration> methods) {
		for(MethodDeclaration m : methods) {
			if(m.getName().toString().equals(method.getName().toString())) {
				return m;
			}
		}
		return null;
	}
	
	public static void main(String[] args) {
		String testFile = "./tests/sketch/specs/annotation/ClassUseRepTest.java";
		CompilationUnit unit = ASTUtils.astRoot(testFile);
		CompilationUnit newUnit = MethodRepInstanitiations.getCompilationUnit("sketch.specs.annotation.ClassUseRepTest", testFile);
		System.out.println(newUnit);
	}
}

/**
 * There is some limitations in this implementation, it could only
 * used to replace SimpleName to SimpleName, and string literal to
 * string literal.
 * */
class NameReplacementVisitor  extends NaiveASTFlattener {
	public final Map<String, String> mappings;
	public NameReplacementVisitor(Map<String, String> mappings) {
		this.mappings = mappings;
	}
	
	public int replacenum = 0;
	
	@Override
	public boolean visit(SimpleName name) {
		String nameString = name.toString();
		if(!mappings.containsKey(nameString)) {
			return super.visit(name);
		}
		//this.verifySimpleName(name.getAST(), mappings.get(nameString));
		replacenum++;
		name.setIdentifier(mappings.get(nameString));
		return false;
	}
	
	@Override
	public boolean visit(StringLiteral literal) {
		//System.out.println("the string literal -- : " + literal.getEscapedValue());
		//literal.
		String escapeName = literal.getLiteralValue();
		if(!mappings.containsKey(escapeName)) {
			return super.visit(literal);
		}
		replacenum++;
		literal.setLiteralValue(mappings.get(escapeName));
		return false;
	}
}